/*
 * ml20dll
 * This source code is under the GNU Public License (GPL).
 * Written by Ramiro Polla
 * Initial release: july 14th, 2005
 *
 * this dll reads ml20 files
 */

#include "ml20dll.h"

static int is_frame_complete( udps *tmp );
static void fit_before( udps *new_u, udps *tmp_u );
static void fit_after( udps *new_u, udps *tmp_u );
static void switch_free( udps *new_u, udps *tmp_u );

udps *udp_next_keyframe( udps *frame, FILE *in, ML20F *ml20f )
{
    udps *tmp;
    for( tmp = (udps *) udp_next_frame( frame, in, ml20f ) ; tmp != (udps *) -1 ; tmp = (udps *) udp_next_frame( tmp->next, in, ml20f ) )
        if( IS_SET( ml20f->h.ff, 1 ) )
            break;

    if( tmp == (udps *) -1 )
        return (udps *) -1;

    return tmp;
}

udps *udp_next_frame( udps *tmp, FILE *in, ML20F *ml20f )
{
    udps *tmp_u = tmp;

    while( 1 ) {
        if( !tmp_u )
            break;

        if( tmp_u->udp_hdr.frame_part == 0 )
            if( is_frame_complete( tmp_u ) )
                break;

        tmp_u = tmp_u->next;
    }

    if( tmp_u ) {
        fseek( in, tmp_u->filepos, SEEK_SET );
        fread( &ml20f->h, 1, sizeof(ML20H), in );
        { // Correctly set the keyframe bit.
            ml20_int_h temp;
            int position = ftell( in );
            fread( &temp, 1, sizeof(temp), in );
            if( !IS_SET( temp.nkf, 1 ) )
                SET_BIT( ml20f->h.ff, 1 );
            fseek( in, position, SEEK_SET );
        }
        return tmp_u;
    }

    return (udps *) -1;
}

static int is_frame_complete( udps *tmp )
{
    int i;
    int i2 = tmp->udp_hdr.frame_tot;
    int i3 = tmp->udp_hdr.frame_num;

    //return 1 if the frame is complete
    for( i = 0 ; tmp != 0 ; tmp = tmp->next, i++ ) {
        if( (tmp->udp_hdr.frame_part != i) || (tmp->udp_hdr.frame_num != i3) )
            return 0;
        if( i == i2-1 )
            return 1;
    }

    return 0;
}

static void fit_before( udps *new_u, udps *tmp_u )
{
    new_u->previous = tmp_u->previous;
    if( new_u->previous )
        new_u->previous->next = new_u;

    new_u->next = tmp_u;
    tmp_u->previous = new_u;
}

static void fit_after( udps *new_u, udps *tmp_u )
{
    new_u->next = tmp_u->next;
    if( tmp_u->next )
        tmp_u->next->previous = new_u;

    tmp_u->next = new_u;
    new_u->previous = tmp_u;
}

static void switch_free( udps *new_u, udps *tmp_u )
{
    new_u->previous = tmp_u->previous;
    new_u->next = tmp_u->next;

    if( new_u->previous )
        new_u->previous->next = new_u;
    if( new_u->next )
        new_u->next->previous = new_u;
    free(tmp_u);
}

udps *read_udps( FILE *in, int pos )
{
    udps *new_udps = calloc(1, sizeof(udps));
    fseek( in, pos, SEEK_SET );
    if( !fread( &new_udps->length, sizeof( uint32_t ), 1, in ) )
        return 0;
    if( !fread( &new_udps->udp_hdr, sizeof( struct udpinthdr ), 1, in ) )
        return 0;
    new_udps->filepos = ftell( in );
    new_udps->size = new_udps->length - sizeof(struct udpinthdr);
    if( fseek( in, new_udps->size, SEEK_CUR ) )
        return 0;
    return new_udps;
}

void clean_udps( udps *first_u )
{
    udps *tmp_u;
    udps *p_u;

    tmp_u = first_u;
    p_u = first_u->next;
    while( tmp_u ) {
        free( tmp_u );
        tmp_u = p_u;
        if( tmp_u )
            p_u = tmp_u->next;
    }
}

//return
// -1 if first_u
// 0 if in between
// 1 if last_u
void fit_udps_part( udps **first_u, udps **last_u, udps *new_u )
{
    udps *tmp_u;
    int ret = 0;

    for( tmp_u = *last_u ; tmp_u != 0 ; tmp_u = tmp_u->previous ) {
        int8_t dif_num = new_u->udp_hdr.frame_num - tmp_u->udp_hdr.frame_num;

        if( !dif_num ) {
            if( new_u->udp_hdr.frame_part == tmp_u->udp_hdr.frame_part ) {
                ret = 1;
                switch_free( new_u, tmp_u );
                break;
            } else if( new_u->udp_hdr.frame_part > tmp_u->udp_hdr.frame_part ) {
                ret = 1;
                fit_after( new_u, tmp_u );
                break;
            }
        } else
        if( dif_num > 0 ) {
            ret = 1;
            fit_after( new_u, tmp_u );
            break;
        }
    }

    if( ret == 0 )
        fit_before( new_u, *first_u );

    if( !new_u->next )
        *last_u = new_u;
    else if( !new_u->previous )
        *first_u = new_u;
}
